Skip to content

fix(plugins): Drop mutex between token-exchange and token-broker - #404

Merged
huang195 merged 1 commit into
rossoctl:mainfrom
huang195:fix/unmark-token-handlers-mutually-exclusive
May 21, 2026
Merged

fix(plugins): Drop mutex between token-exchange and token-broker#404
huang195 merged 1 commit into
rossoctl:mainfrom
huang195:fix/unmark-token-handlers-mutually-exclusive

Conversation

@huang195

Copy link
Copy Markdown
Member

Summary

Narrow revert addressing @davidhadas's review feedback on #398 /
PR #400: token-exchange and token-broker should be allowed to coexist
in the same outbound chain. Their interaction needs per-route
arbitration
("which handler runs for this specific host?"), not
per-chain mutual exclusion.

PR #400 shipped the wrong diagnosis. It treated the two plugins'
overlap on the Authorization header as a chain-scoped claim
conflict and locked them out of coexistence at plugins.Build
time. As David points out on #398, a single deployment may
legitimately need both — token-exchange for Keycloak-audience
hosts, token-broker for federated-broker hosts, routed per-host.

What this PR does

Reverts the two-plugin mutex. Specifically:

  • tokenexchange/plugin.goCapabilities() returns empty
    PluginCapabilities; contracts import dropped.
  • tokenbroker/plugin.go — same.
  • plugins/plugins_test.go — removes
    TestBuild_TokenExchangeAndTokenBroker_ConflictingClaims (the
    integration test that locked in the mutex) and the tokenbroker
    side-effect import that only existed for that test.

What this PR deliberately does NOT do

  • Does not remove PluginCapabilities.Claims field or the
    validateRelationships Claims loop or its tests in
    registry_test.go. The framework-level mutex mechanism is
    general-purpose; a future plugin with a genuine chain-wide
    conflict (not this route-scoped one) may declare it. No in-tree
    plugin currently does.
  • Does not remove contracts.ClaimAuthorizationHeader. Kept as
    a dormant constant so that a future plugin declaring this
    specific claim doesn't have to re-invent the vocabulary.
  • Does not ship route-level arbitration. That's the actual
    design David proposed in feature: Core Arbitration for Overlapping Plugin Responsibilities in Kagenti AuthBridge #398 (responsibility strings + plugin-
    ordered route precedence, with an explicit passthrough plugin as
    the catch-all). It's a separate PR, pending design alignment on
    the issue thread.

Behavior

After this lands:

Test plan

  • go test -race ./... in authbridge/authlib — passes
  • go test -race ./... in authbridge/cmd/authbridge — passes
  • gofmt -l clean on touched files

Relates to #398.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

PR rossoctl#400 migrated both plugins to declare
`Claims: []string{contracts.ClaimAuthorizationHeader}`, which made
plugins.Build reject any chain that contained both. On review, David
pointed out this diagnosis was wrong: token-exchange and token-broker
have legitimate coexistence use cases (the former handles Keycloak-
audience hosts, the latter handles federated-broker hosts, same
deployment may need both on different routes). The correct fix for
their interaction is per-route arbitration — "which handler runs for
this specific host" — not per-chain mutual exclusion. Route-scoped
arbitration is a separate design (still open on rossoctl#398).

This commit is the narrow revert that restores coexistence. Reverts:

  - tokenexchange/plugin.go: Capabilities() returns empty
    PluginCapabilities; contracts import dropped.
  - tokenbroker/plugin.go: same.
  - plugins_test.go: removes
    TestBuild_TokenExchangeAndTokenBroker_ConflictingClaims (the
    integration test that locked in the mutex) and the tokenbroker
    side-effect import that only existed for that test.

Retained on purpose (no behavior change, no consumer today):

  - PluginCapabilities.Claims field + validateRelationships Claims
    loop + related tests in registry_test.go — the framework-level
    mutex mechanism is general-purpose and may have valid uses for
    plugins that genuinely conflict chain-wide rather than per-
    route. No in-tree plugin currently declares Claims.
  - authlib/contracts/claims.go + ClaimAuthorizationHeader constant —
    kept so that a future plugin declaring this specific claim
    doesn't have to re-invent the vocabulary. Documented as
    unreferenced-by-in-tree-plugins to avoid signal confusion.

Operationally: after this lands, configuring both token-exchange and
token-broker in the same outbound chain becomes the operator's
responsibility to partition cleanly across routes — same as the
pre-rossoctl#400 status quo. The plugins' own OnRequest behavior is
unchanged by this revert (it was also unchanged by rossoctl#400 — the
mutex was build-time only).

Relates to rossoctl#398.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
@huang195
huang195 force-pushed the fix/unmark-token-handlers-mutually-exclusive branch from 7d315e5 to 05e0fd0 Compare May 13, 2026 12:04
@davidhadas

Copy link
Copy Markdown
Contributor

/lgtm

@huang195
huang195 merged commit 8ec47a8 into rossoctl:main May 21, 2026
17 checks passed
@huang195
huang195 deleted the fix/unmark-token-handlers-mutually-exclusive branch May 21, 2026 11:52
@github-project-automation github-project-automation Bot moved this from New /:ToDo to Done in Rossoctl Issue Prioritization May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants